# backend/utils/security.py
import hashlib
import os
import binascii
from datetime import datetime, timedelta
from jose import JWTError, jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from backend.utils.config import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES, PBKDF2_ITERATIONS


# 使用 PBKDF2 进行密码哈希
def get_password_hash(password: str) -> str:
    """使用 PBKDF2-HMAC-SHA256 生成密码哈希"""
    # 生成随机盐
    salt = os.urandom(16)

    # 进行 100,000 次迭代 (符合 NIST 标准)
    dk = hashlib.pbkdf2_hmac(
        'sha256',
        password.encode('utf-8'),
        salt,
        PBKDF2_ITERATIONS
    )

    # 格式: 算法$迭代次数$盐$哈希值
    return f"pbkdf2_sha256${PBKDF2_ITERATIONS}${binascii.hexlify(salt).decode()}${binascii.hexlify(dk).decode()}"


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """验证密码是否匹配哈希值"""
    try:
        # 解析哈希值格式
        algorithm, iterations, salt, hash_val = hashed_password.split('$')

        # 验证算法
        if algorithm != "pbkdf2_sha256":
            return False

        # 将十六进制字符串转换为字节
        salt_bytes = binascii.unhexlify(salt)
        hash_bytes = binascii.unhexlify(hash_val)

        # 使用相同参数计算哈希
        dk = hashlib.pbkdf2_hmac(
            'sha256',
            plain_password.encode('utf-8'),
            salt_bytes,
            int(iterations)
        )

        # 使用恒定时间比较防止时序攻击
        return constant_time_compare(dk, hash_bytes)
    except:
        return False


def constant_time_compare(val1: bytes, val2: bytes) -> bool:
    """恒定时间比较函数，防止时序攻击"""
    if len(val1) != len(val2):
        return False

    result = 0
    for x, y in zip(val1, val2):
        result |= x ^ y

    return result == 0


# 使用标准 OAuth2 流程
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")


def create_access_token(data: dict, expires_delta: timedelta = None) -> str:
    """创建 JWT 访问令牌"""
    to_encode = data.copy()

    # 设置过期时间
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)

    to_encode.update({"exp": expire})

    # 添加额外声明增强安全性
    to_encode.update({
        "iss": "book-sharing-app",  # 签发者
        "aud": "book-sharing-users",  # 受众
        "iat": datetime.utcnow(),  # 签发时间
    })

    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)


async def get_current_user(token: str = Depends(oauth2_scheme)):
    """从 JWT 获取当前用户"""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无法验证凭据",
        headers={"WWW-Authenticate": "Bearer"},
    )

    try:
        # 验证令牌
        payload = jwt.decode(
            token,
            SECRET_KEY,
            algorithms=[ALGORITHM],
            audience="book-sharing-users",
            issuer="book-sharing-app"
        )

        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception

        return username
    except JWTError as e:
        raise credentials_exception


# 在 security.py 中添加令牌刷新功能
def create_refresh_token(data: dict, expires_delta: timedelta = timedelta(days=30)) -> str:
    """创建 JWT 刷新令牌"""
    to_encode = data.copy()

    # 设置过期时间
    expire = datetime.utcnow() + expires_delta
    to_encode.update({"exp": expire})

    # 添加额外声明
    to_encode.update({
        "iss": "book-sharing-app",
        "aud": "book-sharing-refresh",
        "iat": datetime.utcnow(),
        "token_type": "refresh"
    })

    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)


async def refresh_access_token(refresh_token: str):
    """使用刷新令牌获取新的访问令牌"""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无效的刷新令牌",
        headers={"WWW-Authenticate": "Bearer"},
    )

    try:
        # 验证刷新令牌
        payload = jwt.decode(
            refresh_token,
            SECRET_KEY,
            algorithms=[ALGORITHM],
            audience="book-sharing-refresh",
            issuer="book-sharing-app"
        )

        # 验证令牌类型
        if payload.get("token_type") != "refresh":
            raise credentials_exception

        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception

        # 创建新的访问令牌
        access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
        new_access_token = create_access_token(
            data={"sub": username},
            expires_delta=access_token_expires
        )

        return new_access_token
    except JWTError:
        raise credentials_exception
